The following are notes from a refresher on React. The examples are mostly snippets of full components, so there's def stuff missing. However, this was meant as a way to write out what stood out post completion to help solidify things 'refreshed' or newly learned.
Here, in creatElement
the first arg is the element (in this case, div), the second would be an attributes (id/style/etc), and the third would be children (here it is an h1 tag, with no attributes and text). Note: React.createElement
creates one instance of w.e component.
const App = () => {
return React.createElement(
"div",
{},
React.createElement("h1", {}, "Adopt Me!")
);
};
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(React.createElement(App));
const Pet = (props) => {
return React.createElement("div", {}, [
React.createElement("h1", {}, props.name),
React.createElement("h2", {}, props.animal),
React.createElement("h2", {}, props.breed),
]);
};
const App = () => {
return React.createElement("div", {}, [
React.createElement("h1", {}, "Adopt Me!"),
React.createElement(Pet, {
name: "Luna",
animal: "Dog",
breed: "Havanese",
}),
React.createElement(Pet, {
name: "Pepper",
animal: "Bird",
breed: "Cockatiel",
}),
React.createElement(Pet, { name: "Doink", animal: "Cat", breed: "Mix" }),
]);
};
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(React.createElement(App));
This isnt something I'd expect to see much of as-is, but cool to know JSX is basically translating it all. So things start looking more like this (which is what I think we are all used to seeing these days).
const Pet = (props) => {
return (
<div>
<h1>{props.name}</h1>
<h2>{props.animal}</h2>
<h2>{props.breed}</h2>
</div>
);
};
export default Pet;
When you do something like create-react-app
it's like a little starter kit wrapped with a bow :D. With that said, here are some things you probably don't have to think too much about when you hop onto something that already has stuff.
npm install somePackageHere
you do. The latest and greatest versions from the npm registry..eslintrc.json
prettier should go last in the extends[]
that way it turns off whatever may be enabled by what comes before. note: eslint:recommended
is a good starting point..gitignore
file to make sure you are not pushing up anything unnecessarily, ie: node_modules, .env, etc.const [name, setName] = useState('');
All hooks, even custom (made by you hooks) start with 'use' as a way to say 'hey this is a hook'. Also, the structure of the code above is basically destructing, useState returns an array, so here we are saying 'hey, that first thing put it as name and that second thing is gonna be setName'.
State is called in order, this consistency is how React can keep track of where everything is and what has changed in comparison for a re-rendering. With that said, don't try and add them into like a conditional or something because that could mess with the order.
Useful for when trying to synchronize with something external, ie: fetching api, etc. It's like 'hey I want this page rendered and then I want this other thing to happen' the effect .
Now, what's up with that empty array dependency warning from eslint? The dependencies array, when empty is something along the lines of (keeping with the fetch example) you want to make a request after initial load of page, but do not want it getting triggered on every change of name (from the useState exmaple up top). If certain of this wanted behavior, you can silence eslint with // eslint-disable-line react-hooks/exhaustive-deps
. Make sure there's atleast an empty an array in there for this situation, without one it will assume it should run every time the hook changes.
Adding a function inside a useEffect, being defined in there, it isnt recreated on every render cycle.
"Controlled" would mean you have state controlling areas of the form. "Uncontrolled", you would not be setting a value, instead one would listen for submit events that would gather information within the form. Seems a good rule of thumb would be to go for uncontrolled unless you need some sort of dynamic validation (ie: location awareness, autocomplete, etc).
When working with controlled forms, the value attribute should always be present to force value to match state variable whenever it updates (onChange)
Sometimes, reacting to data change isn't what you are after but instead you just want a response to a click. In the case of forms, that could be a submit.
The clicking of the submit button calls the request:
<form
onSubmit={(e) => {
e.preventDefault();
fetchWhateverYouAreFetching()
}}
>
...
The e.preventDefault();
for the forgetful, it prevents or cancels the action like with a 'submit' button.
Note: the 'e' for event in the example above (in react) is a react synthetic event, which is basically the same as a normal DOM event but in cases of using TypeScriptcan be helpful to have that distinction in mind.
"The FormData interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the fetch()..." ref: mdn - FormData
Unless the component is just a pass thru where it truly doesn't matter, best practice is to be explicit of what is being passed/expected. Also, for readablity sake, imo.
<Pet
key={pet.id}
{...pet}
/>
vs.
<Pet
key={pet.id}
name={pet.name}
animal={pet.animal}
breed={pet.breed}
images={pet.images}
location={`${pet.city}, ${pet.state}`}
/>
Dev bundle for React is quite large would suck to have that in production, you don't wanna be all sloooow and stuff out there in the real world... do you?
Using Webpack you would need to be more aware of this, however using Vite (vEEt) or something like Parcel it will auto change the environment variable when you `run build``.
NODE_ENV=development
<=> NODE_ENV=production
<StrictMode><StrictMode/>
wrapping your app with this may be a good idea to get additional warnings on using legacy features or things soon to be deprecated. Something odd about this, it runs functions on initialization 2x... why? NOTE TO SELF, GO FIND OUT WHY
Wrap app with . Inside here whatever pages you want would go inside with each individual page using .
example:
<BrowserRouter>
<header>
<Link to="/">Find-a-Nom</Link>
</header>
<Routes>
<Route path="/details/:id" element={<Details />} />
<Route path="/" element={<SearchParams />} />
</Routes>
</BrowserRouter>
Using <a>
tags cause a rerender of the app due to browser navigating to a new page, use <Link to={
/details/${id}}
to prevent that. <Link>
intercepets and handles cline-side, faster, better UX.
useParams
allows you to capture params from React Router (with earlier example in mind, the :id
). Note: this is possible due to available context from <BrowserRouter>
"it's a built in caching layer for these async data stores", typically you'd want to fetch something and not want to refetch if a user has already visited the page with that same info. So, react-query to the rescue, using React context to pass App's cache.
Wrap app (within BrowserRouter though) with
Note: react query cache is stored in memory. Ref: react query
Higher Order Compoenents tend to be referred to things such as or , they are not in themselves displaying anything but wrappers, providing context to the componenents within that wrap.
Class components are the old school way of doing things in React, although still out in the wild and sometimes may come into play (ie: Error Boundary), the community has migrated more towards using function components.
When writing a handleFn for an onClick within a class component, using an arrow fn helps with scope ensuring the this is the this we are expecting (scope of where fn was defined). In this particular case, it would be the component we are in vs an undefined/etc.
Allows you to store information that isnt exactly needed in the HTML but used for some interaction case within Javascript.
Example:
<img
key={image}
src={image}
alt="animal thumbnail"
**data-index={index}**
/>
"Overall, data-* attributes offer a flexible and convenient way to extend the capabilities of HTML elements, enabling richer interactions and better organization of data within a web page."
+event.target.dataset.index
, DOM stuff comes back as strings.
Error boundaries can only catch errors of child components, so although one may be inclined to wrap from within the return of the component you want "wrapped" it will not work because you are essntially inside the component.
A work-around would be to create a funtion that would then return the ErrorBoundary wrapping the wanted component and then exporting that.
Example:
function DetailsErrorBoundary(props) {
const errMsg = (
<p>
There was an error with this listing. <Link to="/">Click here</Link> to
return to homepage.
</p>
);
return (
<ErrorBoundary componentError={errMsg}>
<Details {...props} />
</ErrorBoundary>
);
}
export default DetailsErrorBoundary;
In this example Details
doesn not actually take any props, however, if it would this would be necessary so they would pass through intended behavior as expected.
In using {...props}
, DetailsErrorBoundary
isnt meant to have an opinion on the props going in, Detail
should care nothing about props as it is meant to pass through seamlessly.
The other thing to note here is the prop/attrubute componentError
, this way the ErrorBoundary
is reusuable and can take in w.e message specific to component.
And where does this prop/attr componentError
come from? Fom withing the ErrorBoundary
class component:
render() {
if (this.state.hasError) {
return this.props.componentError
}
return this.props.children;
}
Static methods, an es6 thing, where you call the method straight from the class in comparison to having to instantiate first.
ErrorBoundary.getDerivedStateFromError()
vs.
eb = new ErrorBoundary()
eb.getDerivedErrorFromState()
Using portals give you the ability to insert a component, in this case, a modal into another area outside the root element in the DOM.
Example (from within an .html):
...
<div id="modal"></div>
<div id="root">not rendered</div>
...
Intrestingly enough I always hear how horrible using modals is and that everyone and their mother should stop using it, but people love them... not going to lie... I sometimes like them too, seeing them (maybe it's cuz something pops up, like OH! Hello there. I don't know :D)
Example (MOdal component):
import { useEffect, useRef } from "react";
import { createPortal } from "react-dom";
const Modal = ({ children }) => {
const elRef = useRef(null);
if (!elRef.current) {
elRef.current = document.createElement("div");
}
useEffect(() => {
const modalRoot = document.getElementById("modal");
modalRoot.appendChild(elRef.current);
// 👇 basically a componentWillUnmount, but for a fn component
return () => modalRoot.removeChild(elRef.current);
}, []);
return createPortal(<div>{children}</div>, elRef.current);
};
export default Modal;
To add to the example above, particularly the return
in the useEffect
:
componentWillUnmount
(used in a class component), but for a fn componentIn the above case, we want to come back to using the ssame div. Kinda like (for those that like cookies), you have a jar that you retrieve to get cookies and when you get more cookies, that same jar is where you want to put them. At least that's how it kinda made sense in my head (eventhough I do not like cookies or have a cookie jar).
elRef.current
it is in fact the same element.useState
, what is returned depends on the state at the time when the function was called, which is something we would want with async calls/effectsExample (modal being used):
...
...
const [showModal, setShowModal] = useState(false);
...
...
return (
...
<button onClick={() => setShowModal(true)}>Adopt {pet.name}</button>
{showModal ? (
<Modal>
<div>
<h1>Would you like to adopt {pet.name}?</h1>
<div className="buttons">
<button>Yes</button>
<button onClick={() => setShowModal(false)}>No</button>
</div>
</div>
</Modal>
) : null}
)
State, not confined to component being called from, but global. This seems useful for smaller amounts of state, however if racking up the amount of context may be better off with something like Redux. From what I gather, there isn't much reason to use both.
When to maybe use something like this? Perhaps, a shopping cart or some user detail for someone logged in, a theme of some sort. Basically, any time you truly need access to some sort of info from various parts of that app.
So, how to do this? react doc - create context
import { createContext } from "react";
const AdoptedPetContext = createContext(null);
export default AdoptedPetContext;
const adoptedPet = useState(null);
return (
<AdoptedPetContext.Provider value={adoptedPet}>
...
...
...
</AdoptedPetContext.Provider>
)
Here, instead of the usual way you see the state hook, destructured, we can pass it fully to use elsewhere in the app as needed.
const [, setAdoptedPet] = useContext(AdoptedPetContext)
...
...
...
return (
...
...
...
<button
onClick={() => {
setAdoptedPet(pet);
}}
>
...
...
)
Here, we are taking out the setter from the state we set up in the previous example. The first part of the destructuring, where we would expect something like adoptedPet
is not necessary here and therefore can stated in this way (or using _
). Now when we click this button, it sets pet object of information for us to grab in another part of the app.
const [adoptedPet] = useContext(AdoptedPetContext)
...
...
...
return (
...
...
{adoptedPet ? (
<div className="pet image-container">
<img src={adoptedPet.images[0]} alt={adoptedPet.name} />
</div>
) : null}
...
...
...
)
Here, one can grab the state that was set previously pet
. It checks for it and only if present will it show this div that has properties off adoptedPet
(pet
object set prior within setAdoptedPet
). In this case, an image that uses the first image and name.
navigate('/')
(navigates programmatically) ommitted in the button where we set state in example 3. Example below.const navigate = useNavigate();
const [, setAdoptedPet] = useContext(AdoptedPetContext)
...
...
...
return (
...
...
...
<button
onClick={() => {
setAdoptedPet(pet);
navigate("/");
}}
>
...
...
)
Ref: React Router Docs - useNavigate
Tools for error monitoring.